/***********************************************************************************
 * 文件名： soft_i2c.c
 * 版本： 1
 * 说明：
 * 		软件模拟I2C。提供单步操作API和按地址读写API。
 *      目前仅提供了同步模式的操作API，需要注意I2C速度一般较慢，软件延时会消耗CPU时间。
 * 		单步操作API使用步骤：Start -> SendByte -> SendByte -> ... -> Stop
 *      在头文件中查看其它 API 以及说明。
 * 修改记录：
 * 	2022/09/13: 初版。刘言。
 ***********************************************************************************/
#include "soft_i2c.h"
#include "soft_i2c_cfg.h"
#include "polsys.h"


// 初始化I2C
void SoftI2c_Init(void)
{
	_SoftI2c_PortInit();
}

//产生IIC起始信号
void SoftI2c_Start(void)
{
	_SoftI2c_SdaOutMode();     //sda线输出
	_SoftI2c_SetSda(1);	  	  
	_SoftI2c_SetScl(1);
	Delay_us(_SOFT_I2C_CYCLE);
 	_SoftI2c_SetSda(0);//START:when CLK is high,DATA change form high to low 
	Delay_us(_SOFT_I2C_CYCLE);
	// _SoftI2c_SetScl(0);
}	  
//产生IIC停止信号
void SoftI2c_Stop(void)
{
	_SoftI2c_SetScl(0);		// SCL低电平后从机停止发送ACK（停止拉低总线）
	_SoftI2c_SetSda(0);
	_SoftI2c_SdaOutMode();//sda线输出
	Delay_us(_SOFT_I2C_CYCLE);
	_SoftI2c_SetScl(1); 
	Delay_us(_SOFT_I2C_CYCLE/2);
	_SoftI2c_SetSda(1);	//STOP:when CLK is high DATA change form low to high
	Delay_us(_SOFT_I2C_CYCLE/2);						   	
}
//等待应答信号到来
//返回值：false，接收应答失败
//        true，接收应答成功
static bool SoftI2c_WaitAck(void)
{
	u8 ucErrTime=0;
	_SoftI2c_SdaInMode();      //SDA设置为输入   
	// _SoftI2c_SetScl(1);
	// Delay_us(10);	 
	_SoftI2c_SetScl(0);		// SCL出现下降沿，从机将开始发送ACK（拉低总线）
	Delay_us(_SOFT_I2C_CYCLE);	// 等待从机发送ACK
	_SoftI2c_SetScl(1);		// SCL高电平读取数据比较符合规定
	Delay_us(_SOFT_I2C_CYCLE/2);
	while(_SoftI2c_GetSda())
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			SoftI2c_Stop();
			return false;
		}
		Delay_us(_SOFT_I2C_CYCLE/2);
	}
	Delay_us(_SOFT_I2C_CYCLE);
	return true;  
}
//产生ACK应答
static void SoftI2c_Ack(void)
{
	_SoftI2c_SetScl(0);//拉低时钟开始数据传输，SDA线上的电平必须只能在SCL为低时才能改变
	Delay_us(_SOFT_I2C_CYCLE/2);	// 等硬件为低电平
	_SoftI2c_SetSda(0);	// ACK
	_SoftI2c_SdaOutMode();
	Delay_us(_SOFT_I2C_CYCLE/2);
	_SoftI2c_SetScl(1);
	Delay_us(_SOFT_I2C_CYCLE);
	// _SoftI2c_SetScl(0);
}
//产生NACK应答		    
static void SoftI2c_NAck(void)
{
	_SoftI2c_SetScl(0);//拉低时钟开始数据传输，SDA线上的电平必须只能在SCL为低时才能改变
	Delay_us(_SOFT_I2C_CYCLE/2);	// 等硬件为低电平
	_SoftI2c_SetSda(1);	// NACK
	_SoftI2c_SdaOutMode();
	Delay_us(_SOFT_I2C_CYCLE/2);
	_SoftI2c_SetScl(1);
	Delay_us(_SOFT_I2C_CYCLE);
	// _SoftI2c_SetScl(0);
}			

//IIC发送一个字节  
bool SoftI2c_SendByte(u8 txd)
{                        
    u8 t;   
	_SoftI2c_SdaOutMode(); 	  
	for(t=0;t<8;t++)
    {
		_SoftI2c_SetScl(0);//拉低时钟开始数据传输，SDA线上的电平必须只能在SCL为低时才能改变
    	Delay_us(_SOFT_I2C_CYCLE/2);
        _SoftI2c_SetSda((txd&0x80)>>7);
        txd<<=1; 	  
		Delay_us(_SOFT_I2C_CYCLE/2);
		_SoftI2c_SetScl(1);
		Delay_us(_SOFT_I2C_CYCLE); 
    }	 
	return SoftI2c_WaitAck();
} 	    
//读1个字节，ack = true 时，发送ACK，ack = false，发送nACK
u8 SoftI2c_ReadByte(bool ack)
{
	u8 i,receive=0;

	_SoftI2c_SdaInMode();//SDA设置为输入
    for(i=0;i<8;i++ )
	{
        _SoftI2c_SetScl(0); // SCL出现下降沿，从机将开始发送数据
        Delay_us(_SOFT_I2C_CYCLE);
		_SoftI2c_SetScl(1); // SCL高电平读取数据比较符合规定
        receive<<=1;
        if(_SoftI2c_GetSda())receive++;   
		Delay_us(_SOFT_I2C_CYCLE); 
    }					 
    if (!ack)
        SoftI2c_NAck();//发送nACK
    else
        SoftI2c_Ack(); //发送ACK   
    return receive;
}

softi2c_result_t SoftI2c_Write(u8 dev_addr, u8 reg_addr, u8 *dat, u8 len)
{
	u8 i;

	SoftI2c_Start();
	if(SoftI2c_SendByte(dev_addr & 0xfe) == false)return SOFTI2C_SDA_FAIL;	// 发送iic从器件地址，最低位0，写操作
	if(SoftI2c_SendByte(reg_addr) == false)return SOFTI2C_SD_FAIL;
	for(i = 0; i < len; i++)
	{
		if(SoftI2c_SendByte(dat[i]) == false)return SOFTI2C_SD_FAIL;
	}
	SoftI2c_Stop();
	return SOFTI2C_OK;
}

softi2c_result_t SoftI2c_Read(u8 dev_addr, u8 reg_addr,u8 *dat, u8 len)
{
	u8 i=0;

	SoftI2c_Start();
	if(SoftI2c_SendByte(dev_addr & 0xfe) == false)return SOFTI2C_SDA_FAIL;	// 发送iic从器件地址，最低位0，写操作
	if(SoftI2c_SendByte(reg_addr) == false)return SOFTI2C_SD_FAIL;
	SoftI2c_Start();
	if(SoftI2c_SendByte(dev_addr | 0x01) == false)return SOFTI2C_SDA_FAIL;	// 发送iic从器件地址，最低位1，读操作
	for(i = 0; i < len; i++)
	{
		dat[i] = SoftI2c_ReadByte(!(i == len - 1));	// 最后一个字节被读出后要发NACK
	}
	SoftI2c_Stop();
	return SOFTI2C_OK;
}



























